﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.ComponentModel;
 

namespace TMRCPUEMULATOR 
{
    /// <summary>
    /// Emulator wraped in a simple classs
    /// </summary>
    class Emulator 
    {

        byte[] memory = new byte[65536];
        byte[] code = new byte[65536];

        /// <summary>
        /// 16 bit program counter
        /// </summary>
        public ushort PC
        {
            get;
            private set;

        }

   

        /// <summary>
        ///  16 bit memory address register
        /// </summary>
        public ushort MAR
        {
            get;
            private set;
        }

        /// <summary>
        /// 16 bit stack pointer
        /// </summary>
        public ushort SP
        {
            get;
            private set;
        } 

        /// <summary>
        /// 8 bit accomulator
        /// </summary>
        public byte ACC
        {
            get;
            private set;
        } 

        /// <summary>
        ///  8 bit operand register
        /// </summary>
        public byte OR
        {
            get;
            private set;
        } 

        /// <summary>
        /// 8 bit flag register
        /// </summary>
        public byte FLAGS
        {
            get;
            private set;
        } 

        /// <summary>
        /// 8 bit instruction register
        /// </summary>
        public byte IR
        {
            get;
            private set;
        } 

        /// <summary>
        /// construct a new emulator
        /// </summary>
        /// <param name="filename">filename of the binary file</param>
        public Emulator(string filename)
        {

            FileStream fs = new FileStream(filename, FileMode.Open);
            BinaryReader br = new BinaryReader(fs);
            code = br.ReadBytes((int)fs.Length);
            // initlize the memory
            for (int i = 0; i < memory.Length; i++)
            {
                memory[i] = 0x00;
            }

            PC = 0x0000;
            MAR = 0x0000;
            SP = 0x2000; //8kb stack
            ACC = 0x00;
            OR = 0x00;
            FLAGS = 0x00;
            IR = 0x00;
 
            br.Close();
            fs.Close();
        }

        /// <summary>
        /// run the emulator
        /// </summary>
        /// <param name="FOSC">oscilator speed</param>
        public void Run(int FOSC)
        {
    /*fetch first 
     * which contains ir_clk and code_oe and inc_pc
     * then any thing
     * last microcde is always next op which is a microstepper reset
     * 
     * 
     * 
     * 
     * 
     * 
     */
            while (true)
            {
                
                IR = code[PC];
                PC++;
                ExcuteOp(IR);

                Thread.Sleep(1000 / FOSC);
            }

        }

        /// <summary>
        /// print current memory
        /// </summary>
        public void PrintMemory()
        {
            foreach (byte item in memory)
            {
                Console.WriteLine("0x" + item.ToString("X2"));
            }
        }

        /// <summary>
        /// print code memory
        /// </summary>
        public void PrintCodeMemory()
        {
            Console.WriteLine("Printing Code");
            for (int i = 0; i < code.Length; i++)
            {
                OpCode current = OpCode.LIST.Find(x => x.Binary==code[i]);


                if (current.AddressMode == ADDRESSMODE.NONE)
                {
                    Console.WriteLine("OpCode {0}, Binary 0x{1}, Code 0x{2}",
                        current.Name,
                        code[i].ToString("X2"),
                        i.ToString("X4"));

                }

                if (current.AddressMode==ADDRESSMODE.Imeddiate1b)
                {
                    Console.WriteLine("OpCode {0}, Binary 0x{1}, Code 0x{2}, Data 0x{3}",
                        current.Name,
                        code[i].ToString("X2"),
                        i.ToString("X4"),
                        code[++i].ToString("X2"));
                  
                }

                if (current.AddressMode == ADDRESSMODE.Direct)
                {
                    i++;
                    ushort address = (ushort)(code[i] << 8);
                    i++;
                    address += (ushort)code[i];

                    Console.WriteLine("OpCode {0}, Binary 0x{1}, Code 0x{2}, Target 0x{3}",
                                            current.Name,
                                            code[i-2].ToString("X2"),
                                            (i - 2).ToString("X4"),
                                            address.ToString("X4"));
                }
            }

        }

        /// <summary>
        /// read memory to wrap reading from devices registers
        /// </summary>
        /// <param name="Address">16-bit address of memory</param>
        /// <returns></returns>
        private byte ReadMemory(ushort Address)
        {
            Console.WriteLine("Memory Read At Address 0x" + Address.ToString("X4"));
            return memory[Address];
        }
        /// <summary>
        /// write memory to wrap writing to devices
        /// </summary>
        /// <param name="Address">16-bit address</param>
        /// <param name="Data">8-bit data</param>
        private void WriteMemory(ushort Address, byte Data)
        {
            Console.WriteLine("Memory Write At Address 0x{0}  Data 0x{1}",
                Address.ToString("X4"),
                Data.ToString("X2"));
            memory[Address] = Data;

        }

        /// <summary>
        /// excute the current operation
        /// </summary>
        /// <param name="OP">8-bit opcode </param>
        private void ExcuteOp(byte OP)
        {
 

            switch (OP)
            {
                case 0x00: //no op                  
                    break;

                case 0x01: //hkt
                    PC--;
                    break;

                case 0x02: //rst
                    PC--;
                    break;

                case 0x03: //subr

                    MAR = (ushort)(code[PC] << 8);
                    PC++;
                    MAR += (ushort)(code[PC]);
                    PC++;

                    WriteMemory(SP, (byte)(PC >> 8));
                    SP--;
                    WriteMemory(SP, (byte)PC);
                    SP--;

                    PC = MAR;

                    break;

                case 0x04: //rtn


                    break;

                case 0x05: //brk
                    PC = 0xFFFE;
                    
                    break;
       
                case 0x06: //DSI 
                    
                    
                    break;
 
         
                case 0x07: //ENI

                    return;

                case 0x08: //LDA

                    MAR = PC;
                    PC++;
                    ACC = code[MAR];

                    return;

                case 0x09: //LDAM

                    MAR = (ushort)(code[PC] << 8);
                    PC++;
                    MAR += (ushort)code[PC];
                    PC++;
                    ACC = ReadMemory(MAR);

                    return;

                case 0x0A://LDPC

                    OR = code[PC]; //code[PC] => OR
                    PC++; 
                    PC = (ushort)((OR << 8) + (code[PC])); // OR => hipc , code => lowpc
    
                    return;

                case 0x0B://LDSP

                    OR = code[PC];
                    PC++;
                    SP = (ushort)((OR << 8) + (code[PC])); // OR => hisp , code => lowsp

                    return;

                case 0x0C: //STA

                    MAR = (ushort)(code[PC] << 8);
                    PC++;
                    MAR += (ushort)code[PC];
                    PC++;
                    WriteMemory(MAR, ACC);

                    return;
                case 0x0D: //STPC

                    MAR = (ushort)(code[PC] << 8);
                    PC++;
                    MAR += (ushort)code[PC];
                    PC++;

                    WriteMemory(MAR, (byte)(PC >> 8));
                    MAR++;

                    WriteMemory(MAR, (byte)(PC));
                    return;

                case 0x0E: //STSP

                    MAR = (ushort)(code[PC] << 8);
                    PC++;
                    MAR += (ushort)code[PC];
                    PC++;


                    WriteMemory(MAR, (byte)(SP >> 8));
                    MAR++;

                    WriteMemory(MAR, (byte)(SP));

                    break;
                case 0x0F: //PSH
                    OR =  code[PC];
                    WriteMemory(SP, OR);
                    PC++;
                    SP--;

                    break;

                case 0x10: //PSHM

                    WriteMemory(SP, code[PC]);
                    PC++;
                    SP--;

                    break;
                case 0x11: 

                    WriteMemory(SP, code[PC]);
                    PC++;
                    SP--;

                    break;
                case 0x12: 

                    WriteMemory(SP, code[PC]);
                    PC++;
                    SP--;

                    break;

                default:
                    Console.WriteLine("Opcode not recognized 0x{0}", code[PC - 1].ToString("X2"));
                    break;
            }
        }

     
    }
}
